#pragma once
#include <iostream>
#include <functional>
#include <unordered_map>
#include "Sock.hpp"
#include "Log.hpp"
#include "Error.hpp"
#include "Epoll.hpp"
#include "Util.hpp"
#include "Protocol.hpp"

/*
    3.1 vs 3.2
    3.2：
    1.为了正确处理读写问题，每一个对应的sock都需要有自己的输入，输出缓冲区，我们需要新增结构！
        这个结构我们命名为connection, 每一个connection 与 fd 是一一对应的关系，互为key值，所以我们
        可以通过unordered_map进行组织
        connection类，既保存了IO的信息，也要能对IO事件进行一个处理-->你需要设置一个回调机制
        读写异常的具体操作，都通过操作connection 来对具体的sock进行操作
    2.epoll的工作模式：
    LT模式：事件就绪，如果上层不处理则一直通知上层
    ET模式：数据从无到有，从有到多，发生变化的时候，只通知一次，倒逼着程序员尽快的处理数据，且要一次性处理完。
    怎么保证数据处理完？ 一直读取，直到读取的数量 < 期待的数量,则说明数据读取完了。
    循环读取 --> recv/send 接口默认是阻塞的 --> 循环会被阻塞住（目前服务器是单进程，单线程的，阻塞住的话，整个服务器都阻塞了）
    --> 所以要非阻塞！
    使用fcntl设置非阻塞！
    如何设置epoll为ET模式： 添加事件 --> EPOLLET
    3. epoll中 读事件是常设置的，写事件是按需设置。
        大部分情况下对端的缓冲区一般都是就绪的，如果epoll把写事件设置为常设置，那么epoll就会一直关心写事件，白白的浪费资源
        只有我们有数据要发送给对端的时候，才把写功能打开
    4. 要解决正确处理读写，需要引入协议！
        之前写过一个网络版本的计算器，在那里我们设计过协议，我们拿来使用

    5. 经过改版后，此时的epoll服务器 被称为Reactor。
    Reactor：底层使用epoll进行驱动，有事件进行事件派发。

*/
class Connection;
class EpollServer;
// 使用协议的命名空间
using namespace ns_Protocol;
using func_t = std::function<void(Connection*,Request)>;
/*
    业务处理的方案是：在上层处理好数据（序列化等行为）后，并把数据放入到输出缓冲区中，并把写开关打开
    因此Connection类内，需要添加一个回指指针，使上层能够回调epollServer中的接口
*/

using callback = std::function<void(Connection*)>;
static const uint16_t defaultport = 8888;
static const int gnums = 1024;


class Connection
{
public:
    Connection(int fd,int events,std::string ip,uint16_t port):
    _fd(fd),
    _events(events),
    _ip(ip),
    _port(port)
    {}

    ~Connection()
    {}

    void Register(callback recver,callback sender,callback excepter)
    {
        _recver = recver;
        _sender = sender;
        _excepter = excepter;
    }

public:
    // IO信息
    int _fd; // sock
    uint32_t _events;    // 事件
    std::string _inbuf;  // 输入缓冲区
    std::string _outbuf; // 输出缓冲区
    // IO处理函数
    callback _recver; // 读事件
    callback _sender; // 写事件
    callback _excepter; // 异常事件

    // 用户信息
    std::string _ip;
    uint16_t _port;
    // 回指指针
    // 在业务处理时，使用到
    EpollServer* _thisEpoll;  
};



class EpollServer
{
public:
    EpollServer(func_t func,uint16_t port = defaultport) :_func(func), _port(port)
    {
    }
    ~EpollServer()
    {
        _listensock.Close();
        _epoll.Close();
    }
    void InitServer()
    {
        // 1.建立tcp通信
        // 1.1 创建套接字
        _listensock.Socket();
        // 1.2 绑定端口
        _listensock.Bind(_port);
        // 1.3 监听
        _listensock.Listen();
        // 2.创建epoll模型
        _epoll.Create();
        // 3.将listensock加入connections中
        // 顺便也把要关心得事件加入到epoll模型中
        // 将epoll更改为ET模式
        AddConnection(_listensock.Fd(),EPOLLIN | EPOLLET);
        logMessage(Info,"epollServer init success..");
    }

    // 事件派发器
    void Dispatcher()
    {
        // 能进行accept吗？
        // 不能，需要让epoll通知我们事件就绪
        int timeout = 1000; // 单位是毫秒
        while (true)
        {
            LoopOnce(timeout);
            std:: cout << "----------" << std::endl;
        }
    }
    // epoll 执行一次：
    void LoopOnce(int timeout)
    {
        // epoll_wait的返回值是 就绪事件的个数
        int n = _epoll.Wait(_revs, gnums, timeout);
        // switch 测试用
        switch (n)
        {
        case 0:
            logMessage(Debug, "no events ready...");
            return;
        case -1:
            logMessage(Warning, "epoll_wait filed...");
            return;
        default:
            logMessage(Info, "有%d个事件就绪\n", n);
            break;
        }
        // 现在我们只关心合法的事件
        for(int i = 0; i < n; i++)
        {
            int fd = _revs[i].data.fd;
            uint32_t events = _revs[i].events;
            Connection* conn = _connections[fd];
            // 在处理对应的事件时，要保证对应的conn是存在的。
            // 因为都是并发的，并不是else的，前2个if中，有一个连接出现异常，
            // 异常的处理情况是释放掉相关结构，此时的conn是个非法的。再往下执行
            // 一定会报错的。
            if(events == EPOLLIN && IsExitConnction(fd))
                conn->_recver(conn);
            if(events == EPOLLOUT && IsExitConnction(fd))
                conn->_sender(conn);
            if(((events & EPOLLERR) || (events & EPOLLHUP)) && IsExitConnction(fd))
                conn->_excepter(conn);
        }

    }

    // 将HandEvent合并到LoopOnce中
    // void HandEvent(int n)
    // {
    //     for (int i = 0; i < n; i++)
    //     {
    //         int fd = _revs[i].data.fd;
    //         uint32_t events = _revs[i].events;
    //         logMessage(Info,"当前正在处理%d上的%s",fd,(events & EPOLLIN) ? "EPOLLIN" :"OTHER");
    //         // 读事件就绪
    //         if (events == EPOLLIN)
    //         {
    //             // 如果是监听套接字
    //             if (fd == _listensock.Fd())
    //                 Accepter();
    //             else
    //                 Recver(fd);
    //         }
    //         // 写事件就绪
    //         if (events == EPOLLOUT)
    //         {
    //             // TODO
    //         }
    //     }
    // }

    // 判断是否在连接集合中
    bool IsExitConnction(int fd)
    {
        return (_connections.find(fd) != _connections.end());
    }
    // 连接器
    void Accepter(Connection* conn)
    {
        // 在ET模式下，并不能保证同一时刻的连接只有一个，可能有很多个连接，也就是说你一次读取是会遗漏的
        // 必须循环读取
        do
        {
            std::string clientip;
            uint16_t clientport;
            int sock = _listensock.Accept(clientip, clientport);
            if(sock < 0)
            {
                if(errno == EAGAIN || errno == EWOULDBLOCK) // accept了，但是你没有数据了，换而言之已经accept完了
                    break;
                else if(errno == EINTR) // accept被信号中断了，重新accept
                    continue;
                else  // 这一次的sock是真的出错了，下一次可能会正确
                    continue;
                logMessage(Warning,"accept error, code %d ,errstring %s",errno,strerror(errno));
            }
            else
                // 加入连接集合中，并让epoll关心
                AddConnection(sock,EPOLLIN,clientip,clientport);
        } while (conn->_events & EPOLLET);
        logMessage(Debug,"accept success...");
    }
    
    // 将新的连接添加连接集合中
    void AddConnection(int fd,int events,std::string ip = "127.0.0.1",uint16_t port = defaultport)
    {
        // 1.将对应的文件描述符设置为非阻塞
        Util::SetNoBlock(fd);
        // 2.创建集合对象
        Connection* conn = new Connection(fd,events,ip,port);
        // 绑定事件处理的方法
        // 3.类内成员函数，被外部回调，需要使用bind进行绑定，因为有this指针
        if(fd == _listensock.Fd())
            conn->Register(std::bind(&EpollServer::Accepter,this,std::placeholders::_1),nullptr,nullptr);
        else
        {
            conn->Register(
                std::bind(&EpollServer::Recver,this,std::placeholders::_1),
                std::bind(&EpollServer::Sender,this,std::placeholders::_1),
                std::bind(&EpollServer::Excepter,this,std::placeholders::_1)
            );
        }
        // 4.添加回指指针
        conn->_thisEpoll = this;
        // 5.建立fd-connection的映射
        _connections.insert(std::pair<int,Connection*>(fd,conn));
        // 6.让epoll关心fd
        _epoll.AddModEvent(EPOLL_CTL_ADD, fd, events);

    }
    // 读写开关
    void EnableReadWrite(Connection* conn,bool readable,bool writeable)
    {
        conn->_events = EPOLLET | (readable ? EPOLLIN : 0) | (writeable ? EPOLLOUT : 0);
        _epoll.AddModEvent(EPOLL_CTL_MOD,conn->_fd,conn->_events);

    }
    
     // 读
    void Recver(Connection* conn)
    {
        // 同理，当epoll是ET模式后，要保证一次读取完所有的数据，得是循环读取，并且要把读取的数据
        // 放到对应fd的缓冲区内部。 一直读，一直读，最后会把fd对应的缓冲区内部的数据读完
        do
        {
            char buf[1024];
            int n = recv(conn->_fd, buf, sizeof(buf) - 1, 0);
            if (n > 0)
            {
                buf[n] = 0;
                conn->_inbuf += buf;
                std::cout <<"_inbuf: " << conn->_inbuf;
                // 3.2 采用的是边读取边解析
                // 1. 获取完整报文
                std:: string requestStr; // 请求
                int n = getPackage(conn->_inbuf,&requestStr);
                if(n <= 0) // 未解析到一个完整的报文
                    continue;
                // 2. 去掉报头
                requestStr = removeHeaders(requestStr,n);
                // 3. 反序列化
                Request request;
                request.Deserialize(requestStr);
                // 4. 业务处理
                // 这里的业务处理方案是，返回一个string，在epoll里进行序列化
                _func(conn,request);

            }
            else if (n == 0)  // 对端关闭，当作异常处理
            {
                logMessage(Info,"peer is close, sock = %d",conn->_fd);
                conn->_excepter(conn);
            }
            else
            {
                logMessage(Warning,"recv error, code %d ,errstring %s",errno,strerror(errno));
                // 读取了，但是没有数据
                if(errno == EAGAIN || errno == EWOULDBLOCK)
                    break;
                else if(errno == EINTR) // 这一次读取被信号中断了
                    continue;
                else // 这一次是真读取错了
                {
                    conn->_excepter(conn);
                    break;
                }

            }
        }while(conn->_events & EPOLLET);


    }
    // 发送
    void Sender(Connection* conn)
    {
        logMessage(Info,"send task start...");
        // epoll在ET模式下，只会通知一次，写事件就绪，对方的缓冲区中可以放数据
        // 那我要一次性，尽可能把对方的缓冲区打满，我要尽量把我的所有数据都发送过去
        do
        {
            int n = send(conn->_fd,conn->_outbuf.c_str(),conn->_outbuf.size(),0);
            if(n > 0)
            {
                // 发送成功,要把_outbuf中发送的部分移除
                conn->_outbuf.erase(0,n);
                std::cout <<"_outbuf: " << conn->_outbuf;
                // 在写的时候我们写事件 epoll是没有关心的。
                // 在epoll中 读事件是常设置，但是写事件是按需设置
                // 因此我们需要一个函数，去控制读写事件 是否被打开
                if(conn->_outbuf.empty())
                {
                    EnableReadWrite(conn,true,false);
                    break;
                }
                else
                    EnableReadWrite(conn,true,true);

            }
            else if(n == 0)
            {
                // 当返回值为0时表示对端关闭
                conn->_excepter(conn);
                break;
            }
            else
            {
                logMessage(Warning,"send error, code %d ,errstring %s",errno,strerror(errno));

                 // 发送了，但是对方的缓冲区已经满了
                if(errno == EAGAIN || errno == EWOULDBLOCK)
                    break;
                else if(errno == EINTR)
                    continue;
                else
                {
                    conn->_excepter(conn);
                    break;
                }
            }
        } while (conn->_events & EPOLLET);
        logMessage(Info,"send task success...");
        
    }
    // 异常处理
    void Excepter(Connection* conn)
    {
        // epoll删除fd
        _epoll.DelEvent(conn->_fd);
        // 关闭对应的文件描述符
        close(conn->_fd);
        // 删除对应的键值对
        _connections.erase(conn->_fd);
        // 释放Connection
        delete conn;
    }


private:
    Sock _listensock;
    uint16_t _port;
    Epoller _epoll;
    // epoll模型中，当有事件就绪时，通过revs告知用户
    struct epoll_event _revs[gnums];
    func_t _func;
    std::unordered_map<int,Connection*> _connections;
};